/* eslint-disable no-unused-vars */
// 容器：包含值和值的变形关系（这个变形关系就是函数）
// 函子：是一种特殊的容器，通过一个普通的对象来实现，该对象具有map方法，map方法可以运行一个函数对值处理
const fs = require('fs');
const path = require('path');
class Container {
  static of(value) {
    return new Container(value);
  }
  constructor(value) {
    this._value = value;
  }
  // map方法始终返回一个函子
  map(fn) {
    return Container.of(fn(this._value));
  }
}
// 总结：
/**
 * 1、函数式编程不直接操作值，而是由函子完成
 * 2、函子就是一个实现了 map 契约的对象
 * 3、可以把函子想象成一个盒子，这个盒子封装了一个值
 * 4、想要处理盒子中的值，我们需要给盒子的 map 方法传递一个处理值的函数（纯函数），有这个函数对值处理
 * 5、最终map方法返回一个包含新值的盒子
 */
Container.of(10).map(x => x * 10);
// Maybe 函子
// 在开发的过程中，会遇到很多错误
// 可以对外部空值情况做处理（空值副作用在允许范围）
class Maybe {
  static of(value) {
    return new Maybe(value);
  }
  constructor(value) {
    this._value = value;
  }
  map(fn) {
    return this.isNothing() ? Maybe.of(null) : Maybe.of(fn(this._value));
  }
  isNothing() {
    return this._value === null || this._value === undefined;
  }
}

// const r = new Maybe(5).map((x) => x + 5).map((x) => x * x);

// console.log(r);

// const r = Maybe.of(null).map((x) => x.toUpperCase());

// console.log(r);

// Either函子
// Either两者中的任何一个，类似于if...else...的处理
// 异常会让函数变得不纯，Either函子可以用来处理异常
class Left {
  static of(value) {
    return new Left(value);
  }
  constructor(value) {
    this._value = value;
  }
  map() {
    return this;
  }
}

class Right {
  static of(value) {
    return new Right(value);
  }
  constructor(value) {
    this._value = value;
  }
  map(fn) {
    return Right.of(fn(this._value));
  }
}

// const r1 = Right.of(12).map((x) => x + 2);
// const r2 = Left.of(12).map((x) => x + 2);

// console.log(r1);
// console.log(r2);

function parseJSON(str) {
  try {
    return Right.of(JSON.parse(str));
  } catch (e) {
    return Left.of({ error: e.message });
  }
}

// const r = parseJSON('{ "name": "zs" }');

// console.log(r);

// r.map((x) => x.name.toUpperCase());

// IO函子
/**
 * 1、_value是一个函数，这里是把函数作为值来处理
 * 2、把不纯的动作存储到_value中，延迟执行这个不纯的操作（惰性执行），包装当前的操作纯
 * 3、把不纯的操作交给调用者来处理
 */

const fp = require('lodash/fp');
class IO {
  static of(x) {
    return new IO(function() {
      return x;
    });
  }
  constructor(fn) {
    this._value = fn;
  }
  map(fn) {
    return new IO(fp.flowRight(fn, this._value));
  }
}
/**
 * IO函子的问题
 */
function readFile1(filename) {
  return new IO(function() {
    return fs.readFileSync(filename, 'utf-8');
  });
}

function print(x) {
  return new IO(() => {
    console.log(x);
    return x;
  });
}

const cat = fp.flowRight(print, readFile1);

// eslint-disable-next-line no-undef
console.log(
  // eslint-disable-next-line no-undef
  cat(path.resolve(__dirname, '../package.json'))
    ._value()
    ._value()
);

// eslint-disable-next-line no-undef
// const r = IO.of(process).map((p) => p.execPath);
// console.log(r);
// console.log(r._value());

// Task函子
// folktale：只提供了一些函数式处理的操作，例如compose、curry等，一些函子 Task、Either、Maybe等
const { compose, curry } = require('folktale/core/lambda');
const { task } = require('folktale/concurrency/task');
// let f = curry(2, (x, y) => x + y);

// console.log(f(2, 3));
// console.log(f(2)(3));

// let f = compose(fp.toUpper, fp.first);

// console.log(f(['one', 'two']));
// task执行之后，会返回一个函子，其实就是一个compose
function readFile(filename) {
  return task(resolver => {
    fs.readFile(filename, 'utf-8', (err, data) => {
      if (err) resolver.reject(err);
      resolver.resolve(data);
    });
  });
}
// 因为是从右到左，所以先注册的函数先执行
// eslint-disable-next-line no-undef
readFile(path.resolve(__dirname, '../package.json'))
  .map(fp.split('\n'))
  .map(fp.find(x => x.includes('version')))
  .run()
  .listen({
    onRejected: err => {
      console.log(err);
    },
    onResolved: value => {
      console.log(value);
    }
  });

// Ponit函子
// 具有of静态方法的函子

// Monad
// 可以变扁的Pointed函子，IO(IO(x))
// 如果一个函子具有join 和 of两个方法并遵守一些定律就是一个Monad

class IOMonad {
  static of(x) {
    return new IOMonad(function() {
      return x;
    });
  }
  constructor(fn) {
    this._value = fn;
  }
  map(fn) {
    return new IOMonad(fp.flowRight(fn, this._value));
  }
  join() {
    return this._value();
  }
  flatMap(fn) {
    return this.map(fn).join();
  }
}
function readFile2(filename) {
  return new IOMonad(function() {
    return fs.readFileSync(filename, 'utf-8');
  });
}

function print2(x) {
  return new IOMonad(() => {
    console.log(x);
    return x;
  });
}
// const r = readFile2(path.resolve(__dirname, '../package.json'));
// console.log(r, r._value);
// const r1 = r.map(fp.toUpper);
// console.log(r1, r1._value);
// const r2 = r1.flatMap(print2);
// console.log(r2);
// eslint-disable-next-line no-undef
readFile2(path.resolve(__dirname, '../package.json'))
  .map(fp.toUpper)
  .map(fp.find(x => x.includes('version')))
  .flatMap(print2)
  .join();
// readFile2(path.resolve(__dirname, '../package.json'))
// IOMonad { _value: function() { return fs.readFileSync(filename, 'utf-8'); } }
// .map(fp.toUpper)
// IOMonad { _value: fp.flowRight(fp.toUpper, funciton() { return fs.readFileSync(filename, 'utf-8'); } }
// .flatMap(print2)
// .map(print2)
// IOMonad { _value: fp.flowRight(() => { return x }, fp.toUpper, funciton() { return fs.readFileSync(filename, 'utf-8'); } }
// .join()
// fp.flowRight(() => { return x }, fp.toUpper, funciton() { return fs.readFileSync(filename, 'utf-8'))();
//
fp.flowRight(console.log, fp.toUpper)('asdasd');
